import { compareObject, ObjectDifference } from './compare';
import { DatabaseSchema } from './schema';

export type SchemaDifference = ObjectDifference<DatabaseSchema>;

/**
 * 架构比较器
 */
export abstract class SchemaComparator {
  abstract ignorePaths: string[];
  protected equalsCompartor(
    defaultSchema: string,
    left: any,
    right: any,
    path: string
  ): boolean | undefined {
    // 比较类型
    if (path === 'tables[].columns[].type') {
      return (
        left?.replace(/ /g, '').toUpperCase() ===
        right?.replace(/ /g, '').toUpperCase()
      );
    }
    if (path === 'tables[].columns[].defaultValue') {
      return left?.toLowerCase() === right?.toLowerCase();
    }
    if (
      [
        'tables[].database',
        'views[].database',
        'sequences[].database',
        'procedures[].database',
        'functions[].database',
      ].includes(path)
    ) {
      return true;
    }
    if (
      [
        'tables[].schema',
        'views[].schema',
        'sequences[].schema',
        'procedures[].schema',
        'functions[].schema',
        'tables[].foreignKeys[].referenceSchema',
      ].includes(path)
    ) {
      return defaultSchema
        ? left || defaultSchema === right || defaultSchema
        : !left || !right || left === right;
    }
    // 不比较种子数据、数据库名、排序规则、数据库批注等属性
    if (['name', 'collate', 'comment', 'tables[].seedData'].includes(path)) {
      return true;
    }
  }

  private isSameSchemaObject(
    defaultSchema: string,
    left: any,
    right: any,
    path: string
  ): boolean {
    if (this.ignorePaths.includes(path)) {
      return true;
    }
    if (
      [
        'schemas',
        'tables[].columns',
        'tables[].foreignKeys',
        'tables[].indexes',
        'tables[].constraints',
        'tables[].primaryKey.columns',
        'tables[].foreignKeys[].columns',
        'tables[].indexes[].columns',
        'tables[].constraints[].columns',
      ].includes(path)
    ) {
      return left.name === right.name;
    }

    if (
      ['tables', 'views', 'sequences', 'procedures', 'functions'].includes(path)
    ) {
      return (
        left.name === right.name &&
        (defaultSchema
          ? (left.schema || defaultSchema) === (right.schema || defaultSchema)
          : !left.schema || !right.schema || left.schema === right.schema)
      );
    }
    throw new Error(`Path error.`);
  }

  compareSchema(
    defaultSchema: string | undefined,
    source: DatabaseSchema | undefined,
    target: DatabaseSchema | undefined
  ): SchemaDifference | null {
    return compareObject(
      source,
      target,
      this.isSameSchemaObject.bind(this, defaultSchema),
      this.equalsCompartor.bind(this, defaultSchema)
    );
  }
}
